package com.bumptech.glide.util;

import android.support.annotation.NonNull;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Prevents {@link InputStream InputStreams} from overflowing their buffer by reading data past
 * their read limit.
 * <p>
 * 预防读取的数据超过了缓存界限而数据溢出的{@link InputStream}的子类
 */
public class MarkEnforcingInputStream extends FilterInputStream {
    private static final int UNSET = Integer.MIN_VALUE;
    private static final int END_OF_STREAM = -1;

    private int availableBytes = UNSET;

    public MarkEnforcingInputStream(@NonNull InputStream in) {
        super(in);
    }

    @Override
    public synchronized void mark(int readLimit) {
        super.mark(readLimit);
        availableBytes = readLimit;
    }

    @Override
    public int read() throws IOException {
        if (getBytesToRead(1) == END_OF_STREAM) {
            return END_OF_STREAM;
        }

        int result = super.read();
        updateAvailableBytesAfterRead(1 /* bytesRead */);
        return result;
    }

    @Override
    public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
        int toRead = (int) getBytesToRead(byteCount);
        if (toRead == END_OF_STREAM) {
            return END_OF_STREAM;
        }

        int read = super.read(buffer, byteOffset, toRead);
        updateAvailableBytesAfterRead(read);
        return read;
    }

    @Override
    public synchronized void reset() throws IOException {
        super.reset();
        availableBytes = UNSET;
    }

    @Override
    public long skip(long byteCount) throws IOException {
        long toSkip = getBytesToRead(byteCount);
        if (toSkip == END_OF_STREAM) {
            return 0;
        }

        long read = super.skip(toSkip);
        updateAvailableBytesAfterRead(read);
        return read;
    }

    @Override
    public int available() throws IOException {
        return availableBytes == UNSET ? super.available()
                : Math.min(availableBytes, super.available());
    }

    private long getBytesToRead(long targetByteCount) {
        if (availableBytes == 0) {
            return END_OF_STREAM;
        } else if (availableBytes != UNSET && targetByteCount > availableBytes) {
            return availableBytes;
        } else {
            return targetByteCount;
        }
    }

    private void updateAvailableBytesAfterRead(long bytesRead) {
        if (availableBytes != UNSET && bytesRead != END_OF_STREAM) {
            // See https://errorprone.info/bugpattern/NarrowingCompoundAssignment.
            availableBytes = (int) (availableBytes - bytesRead);
        }
    }
}
